import SendBird from 'sendbird';
import { Device } from '../../services/device';
import { SendBirdContact } from './sendbird-contact';
import { SendBirdError, SendBirdInstance, User, GroupChannel } from 'sendbird';
import {
  DeviceState,
  ChannelType,
  DeviceErrorType,
  DirectionType,
  ContactState,
  ContactEventType,
  DeviceEventType
} from '../../common/commlib.enums';
import { SendBirdAdditionalData } from './sendbird-additionaldata';
import { ICommunicationLibrary } from '../../..';
import { IContact } from '../../common/commlib.interfaces';

export class SendBirdDevice extends Device {
  public sendbirdInstance: SendBirdInstance = null;

  private constructor(parameters: any, commlib?: ICommunicationLibrary) {
    super(parameters.appId || 'SAP-SENDBIRD-DEVICE', commlib);
    this.sendbirdInstance = this.sendbirdInstance
      ? this.sendbirdInstance
      : new SendBird({ appId: parameters.appId });
    this.type = ChannelType.CHAT;
    this.state = DeviceState.DISCONNECTED;
    this.additionalData = new SendBirdAdditionalData(
      this.sendbirdInstance,
      parameters
    );
    this.subscribeEvents();
  }

  public static init(
    deviceParameters: any,
    commlib?: ICommunicationLibrary
  ): SendBirdDevice {
    return new SendBirdDevice(deviceParameters, commlib);
  }

  /**
   * Connect to sendbird server with an unique ID
   * @param userId the ID should be unique within a SendBird application to be distinguished from each other
   * @anotherNote Fex. a hashed email address or phone number in your service.
   */
  public register(parameters?: any): void {
    if (!parameters || !parameters.userId) {
      console.error('Invalid userId, the userId is mandatory.');
      return;
    }

    this.user = `${parameters.userId}`; // convert to string
    this.changeState(DeviceState.CONNECTING);
    this.sendbirdInstance.connect(
      this.user,
      (_user: User, error: SendBirdError) => {
        if (error) {
          this.errorHandler(
            DeviceErrorType.REGISTER_FAILED,
            'Connect to sendbird failed.',
            error
          );
          this.changeState(DeviceState.DISCONNECTED);
          return;
        }
        this.name = `SendBird-Device-${this.user}`;
        this.changeState(DeviceState.CONNECTED);
      }
    );
  }

  /**
   * Disconnect current device, that means user will not be able to join chat channel
   */
  public unregister(): void {
    if (!this.user) {
      console.error('Please register device first');
      return;
    }
    this.sendbirdInstance.disconnect(() => {
      console.log('Sendbird device is disconnected');
      this.user = '';
      this.changeState(DeviceState.DISCONNECTED);
    });
  }

  /**
   *
   * @param state The device state you want to set
   */
  public setState(state: DeviceState): void {
    if (this.state === state) {
      console.log('State must different with before.');
      return;
    }

    if (!this.user) {
      console.error('Please register device first.');
      return;
    }

    switch (state) {
      case DeviceState.NOTROUTABLE:
        this.register(this.user);
        break;

      case DeviceState.ROUTABLE:
        this.unregister();
        break;

      default:
        break;
    }
  }

  public createContact(parameters?: any): Promise<IContact> {
    return new Promise((resolve, reject) => {
      if (!this.sendbirdInstance || !this.user) {
        reject(new Error('Please register SendBird device first.'));
      } else {
        const params = new this.sendbirdInstance.GroupChannelParams();
        params.isPublic = Boolean(parameters.isPublic);
        params.isDistinct = Boolean(parameters.isDistinct);
        params.addUserIds([this.user]);
        params.name = parameters.name || '';

        this.sendbirdInstance.GroupChannel.createChannel(
          params,
          (groupChannel: GroupChannel, error: SendBirdError) => {
            if (error) {
              reject(new Error(`Create contact failed with error: ${error}`));
            } else {
              const newContact = new SendBirdContact(
                DirectionType.OUTBOUND,
                groupChannel,
                this,
                ContactState.CONNECTING
              );
              this.contacts.push(newContact);
              this.onNewEvent$.next({
                eventType: ContactEventType.NEW,
                payload: newContact
              });

              resolve(newContact);
            }
          }
        );
      }
    });
  }

  public joinContact(contactId: string): Promise<IContact> {
    return new Promise((resolve, reject) => {
      if (!this.sendbirdInstance || !this.user) {
        reject(new Error(`Please register SendBird device first.`));
      } else {
        this.sendbirdInstance.GroupChannel.getChannel(
          contactId,
          (groupChannel: GroupChannel, error1: SendBirdError) => {
            if (error1) {
              reject(new Error(`Search contact failed with error: ${error1}`));
            } else {
              const newContact = new SendBirdContact(
                DirectionType.INBOUND,
                groupChannel,
                this,
                ContactState.CONNECTED
              );
              this.contacts.push(newContact);
              this.onNewEvent$.next({
                eventType: ContactEventType.CONNECTED,
                payload: newContact
              });

              resolve(newContact);
            }
          }
        );
      }
    });
  }

  public getSendBirdInstance(): SendBirdInstance {
    return this.sendbirdInstance;
  }

  private changeState(newState: DeviceState) {
    this.state = newState;
    this.onNewEvent$.next({
      eventType: DeviceEventType.CHANGE,
      payload: this
    });
  }

  private subscribeEvents(): void {
    const reconnectSucHandler = new this.sendbirdInstance.ConnectionHandler();
    reconnectSucHandler.onReconnectSucceeded = this.onReconnectSuc.bind(this);
    this.sendbirdInstance.addConnectionHandler(
      'CS_COMMLIB_RECONNECT_STARTED_HANDLER_ID',
      reconnectSucHandler
    );
  }

  private onReconnectSuc(): void {
    this.onNewEvent$.next({
      eventType: DeviceEventType.RECONNECTED,
      payload: this
    });
  }

  private errorHandler(type: DeviceErrorType, msg: string, error: any) {
    if (!error || !msg) {
      return;
    }
    console.error(msg);
    this.onError$.next({
      type: type,
      message: msg,
      payload: error
    });
  }
}
